<?php

declare(strict_types=1);

namespace Dreamcat\ApiDocTest\TestCases;

use Dreamcat\ApiDoc\ApiSaver\YapiSaver;
use Dreamcat\ApiDoc\Enums\YapiMergeMode;
use Dreamcat\ApiDoc\Error\SaverException;
use Dreamcat\ApiDoc\Popo\ApiDefinition;
use Dreamcat\ApiDoc\Swagger\Define2Swagger;
use Exception;
use Laminas\Diactoros\ResponseFactory;
use Laminas\Diactoros\StreamFactory;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\Constraint\IsEqual;
use PHPUnit\Framework\TestCase;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Throwable;

/**
 * yapi保存器的测试用例
 * @author vijay
 */
class YapiSaverTest extends TestCase
{
    private string $urlBase = "http://yapi.demo.com";
    private string $token = "a8nei74nv098734";

    public function testWithMock()
    {
        $apiDef = $this->createApiDefine();
        $result = [
            "errcode" => 0,
            "errmsg" => "成功导入",
        ];
        $response = (new ResponseFactory())
            ->createResponse()
            ->withBody((new StreamFactory())->createStream(json_encode($result)));
        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->with(
                new Callback(
                    function ($request) use ($apiDef) {
                        return $this->assertEqualRequest($request, $apiDef, YapiMergeMode::MERGE, "/api");
                    }
                )
            )->willReturn($response);
        $saver = new YapiSaver($this->urlBase, $this->token, $client);
        $saver->setBasePath("/api");
        self::assertEquals("/api", $saver->getBasePath());
        $saver->start();
        $saver->saveApiDefinition($apiDef);
        $saver->end();
    }

    public function testErrorCode()
    {
        $code = mt_rand(400, 599);

        $this->expectException(SaverException::class);
        $this->expectExceptionCode(SaverException::UNEXPECT_HTTP_CODE);
        $this->expectExceptionMessage("请求状态码 {$code} 与预期不符");

        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->with(
                new Callback(
                    function ($request) {
                        return $this->assertEqualRequest($request, null, YapiMergeMode::GOOD);
                    }
                )
            )
            ->willReturn((new ResponseFactory())->createResponse($code));
        $saver = new YapiSaver($this->urlBase, $this->token, $client, YapiMergeMode::GOOD());
        $saver->start();
        $saver->end();
    }

    public function testEmptyBody()
    {
        $this->expectException(SaverException::class);
        $this->expectExceptionMessage("未得到响应体");
        $this->expectExceptionCode(SaverException::EMPTY_HTTP_BODY);

        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->willReturn((new ResponseFactory())->createResponse());
        $saver = new YapiSaver($this->urlBase, $this->token, $client);
        $saver->start();
        $saver->end();
    }

    public function testInvalidBody()
    {
        $this->expectException(SaverException::class);
        $this->expectExceptionMessage("响应体json解析失败 2 State mismatch (invalid or malformed JSON)");
        $this->expectExceptionCode(SaverException::INVALID_JSON_HTTP_BODY);

        $response = (new ResponseFactory())
            ->createResponse()
            ->withBody((new StreamFactory())->createStream("[{]}"));
        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->willReturn($response);
        $saver = new YapiSaver($this->urlBase, $this->token, $client);
        $saver->start();
        $saver->end();
    }

    public function testErrorBody()
    {
        $this->expectException(SaverException::class);
        $this->expectExceptionMessage("请求失败 msg = 导入失败");
        $this->expectExceptionCode(SaverException::INVALID_BUSI_CODE);

        $result = [
            "errcode" => 10,
            "errmsg" => "导入失败",
        ];
        $response = (new ResponseFactory())
            ->createResponse()
            ->withBody((new StreamFactory())->createStream(json_encode($result)));
        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->willReturn($response);
        $saver = new YapiSaver($this->urlBase, $this->token, $client);
        $saver->start();
        $saver->end();
    }

    public function testRequestError()
    {
        $error = new class(uniqid("error"), mt_rand(1, 100)) extends Exception implements ClientExceptionInterface {
        };
        $client = $this->getMockForAbstractClass(ClientInterface::class);
        $client->expects(self::once())
            ->method("sendRequest")
            ->willThrowException($error);
        try {
            $saver = new YapiSaver($this->urlBase, $this->token, $client);
            $saver->start();
            $saver->end();
        } catch (SaverException $exception) {
            self::assertEquals(SaverException::HTTP_EXCEPTION, $exception->getCode());
            self::assertEquals($error->getMessage(), $exception->getMessage());
            self::assertEquals($error, $exception->getPrevious());
        }
    }

    /**
     * 判断请求是否相同
     * @param mixed $request
     * @param ?ApiDefinition $apiDef
     * @param string $mergeMode
     * @param string $basePath
     * @return bool
     */
    private function assertEqualRequest(
        mixed $request,
        ?ApiDefinition $apiDef,
        string $mergeMode,
        string $basePath = "/"
    ): bool {
        if (!($request instanceof RequestInterface)) {
            return false;
        }
        if ($request->getUri() != $this->urlBase . "/api/open/import_data") {
            return false;
        }
        if ($request->getMethod() != "POST") {
            return false;
        }
        if ($request->getHeader("Content-Type") != ["application/x-www-form-urlencoded"]) {
            return false;
        }
        $body = strval($request->getBody());
        parse_str($body, $result);
        $tools = new Define2Swagger("yapi-saver");
        $tools->setBasePath($basePath);
        if ($apiDef) {
            $tools->addApiDefine($apiDef);
        }
        $data = [
            "type" => "swagger",
            "json" => json_encode($tools->getJson()),
            "merge" => $mergeMode,
            "token" => $this->token,
        ];
        $constraint = new IsEqual($data);
        try {
            $constraint->evaluate($result);
            $ok = true;
        } catch (Throwable) {
            $ok = false;
        }

        return $ok;
    }

    private function createApiDefine(): ApiDefinition
    {
        $apiDef = new ApiDefinition();
        $apiDef->setUri(uniqid("/api/create"))
            ->setMethod("POST")
            ->setDesc("desc")
            ->setName("测试接口");
        return $apiDef;
    }
}

# end of file
